﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: ChiSquareDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using BOL.Maths.Functions;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a chi-square with nu degrees of freedom.
    /// <see>Mathematica: ref/ChiSquareDistribution</see>
    /// <see>http://en.wikipedia.org/wiki/Chi_square_distribution</see>
    /// </summary>
    public sealed class ChiSquareDistribution : IUnivariateDistribution<double>, IEquatable<ChiSquareDistribution>
    {
        #region Private variables

        private int _nu; // number of degree of freedom
        private double _halfNu;

        #endregion

        #region Public properties

        /// <summary>Gets or sets the number of degree of freedom of a chi-square distribution.</summary>
        public int Nu
        {
            get { return _nu; }
            set
            {
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value", "nu is expected to be a positive integer.");

                _nu = value;
                _halfNu = value / 2.0;
            }
        }
        /// <summary>Gets the range of a chi-square distribution.</summary>
        public IRange<double> Domain { get { return new Range<double>(0, double.PositiveInfinity); } }

        /// <summary>Gets the mean of a chi-square distribution.</summary>
        public double Mean { get { return _nu; } }

        /// <summary>Gets the median of a chi-square distribution.</summary>
        public double Median { get { return _nu * Math.Pow((1 - 2 / (double)(9 * _nu)), 3); } }

        /// <summary>Gets the mode of a chi-square distribution.</summary>
        public double Mode { get { return Math.Max(_nu - 2, 0); } }

        /// <summary>Gets the variance of a chi-square distribution.</summary>
        public double Variance { get { return 2 * _nu; } }

        /// <summary>Gets the skewness of a chi-square distribution.</summary>
        public double Skewness { get { return Math.Sqrt(8 * _nu); } }

        /// <summary>Gets the kurtosis of a chi-square distribution.</summary>
        public double Kurtosis { get { return 12 / (double)_nu; } }

        /// <summary>Gets the entropy of a chi-square distribution.</summary>
        public double Entropy { get { return _halfNu + Math.Log(2 * DistributionFunctions.Gamma(_halfNu)) + (1 - _halfNu) * DistributionFunctions.DiGamma(_halfNu); } }

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiates a chi-square distribution.
        /// </summary>
        /// <param name="nu">degree of freedom</param>
        public ChiSquareDistribution(int nu)
        {
            if (nu <= 0)
                throw new ArgumentOutOfRangeException("nu", "nu is expected to be a positive integer.");
            
            _nu = nu;
            _halfNu = nu / 2.0;
        }

        #endregion

        #region ICloneable implementation

        public ChiSquareDistribution Clone()
        {
            return new ChiSquareDistribution(_nu);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
        
        #endregion

        #region Public methods

        /// <summary>
        /// Returns the chi square density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>chi square density</returns>
        public double Pdf(double value)
        {
            return value > 0 ? Math.Exp(-value / 2.0) * Math.Pow(value, _halfNu - 1) / DistributionFunctions.Gamma(_halfNu) / Math.Pow(2, _halfNu) : 0;
        }

        /// <summary>
        /// Returns the cummulative chi square density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative chi square density</returns>
        public double Cdf(double value)
        {
            return DistributionFunctions.IncompleteGamma(_halfNu, value / 2.0);
        }

        /// <summary>
        /// Returns the value of the chi square distribution for given probability.
        /// </summary>
        /// <param name="p">probability</param>
        /// <returns></returns>
        public double Quantile(double p)
        {
            return p.Equals(1) ? Double.MaxValue : 2.0 * new GammaDistribution(_halfNu, 1).Quantile(p);
        }

        /// <summary>
        /// Returns a random value from the chi square distribution.
        /// </summary>
        /// <returns>random value</returns>
        public double Sample(Random r)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Estimates parameters of the chi square distribution using maximum likelihood.
        /// </summary>
        public void MaximumLikelihoodEstimate(IEnumerable<double> source)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Estimates parameters of the chi square distribution using Bayesian methods.
        /// </summary>
        public void BayesianEstimate(IEnumerable<double> source, params dynamic[] priorParameters)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEquatable<T> implementation

        public bool Equals(ChiSquareDistribution other)
        {
            return _nu.Equals(other._nu);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Nu.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is ChiSquareDistribution))
                throw new InvalidCastException("The 'other' argument is not a ChiSquareDistribution object.");
            
            return Equals(other as ChiSquareDistribution);
        }

        public override string ToString()
        {
            return String.Format("Chi-Square(nu = {0})", _nu);
        }

        #endregion
    }
}
